在上一篇文章中,我們介紹了如何使用 React Router 實現頁面導航,並展示了模組化頁面如何提升程式碼的結構性與可維護性。今天,我們將進一步學習如何從零開始打造一個功能完備的 React 導航欄元件,包括響應式設計、動態導航高亮、筆刷效果,以及通過 Sass 的 map-get
函數來管理多種主題樣式,確保在不同設備和主題模式下都能提供優秀的用戶體驗。
導航欄是每個網站的核心部分。打造一個可重用、模組化的導航欄元件能夠讓你在開發中保持一致的設計風格和功能特性,這在大型應用或多頁網站中特別重要。重用導航欄元件不僅能減少重複的開發工作,還能讓你專注於其他複雜的業務邏輯,從而大幅提升開發效率和可維護性。以下是實際的業界案例:
這些案例表明,通過設計導航欄元件庫,能夠有效提高開發速度,確保產品一致性,並長期降低維護成本,最終帶來顯著的經濟效益。
我們的目標是創建一個具有以下功能的 React 導航欄元件:
useTheme()
和 Sass 的 map-get
函數來管理多主題樣式,靈活應對深淺模式。這些功能確保導航欄在各種設備與主題模式下均具一致性和美觀性。
如果對 themeButton
不熟悉,建議回顧 Day 7:進階模組化設計之 ThemeProvider 應用。
首先,我們將新增導航欄元件 Navbar.jsx
的基礎結構,包含品牌區域和主題切換按鈕。我們使用 useTheme()
來管理主題切換,並根據當前主題顯示不同的品牌圖片。
//src/components/navBar/navBar.jsx
const { isDarkMode } = useTheme(); // 獲取當前主題狀態
// 根據主題模式選擇 logo
const logo_light = require('@/assets/logo_light.svg'); // 淺色模式下的 Logo
const logo_dark = require('@/assets/logo_dark.svg'); // 深色模式下的 Logo
return (
<div className={`${styles.navbar}
${isDarkMode ? styles.darkMode : styles.lightMode}`
} >
{/* 導航欄品牌區域 */}
<div className={styles.navbar_brand}>
<a href="/" className="navbar-brand">
<img src={`${isDarkMode ? logo_dark : logo_light}`
} alt="Luma Logo" />
</a>
</div>
{/* 側邊菜單區域 */}
{/* 主題切換按鈕 */}
<ThemeButton className={styles.themeButton} />
{/* 漢堡菜單按鈕 */}
</div >
)
品牌的 Logo 固定在導航欄的左側,主題切換按鈕固定在右側。透過 Flexbox 的 justify-content: space-between
,我們可以讓品牌區域和主題切換按鈕自動分佈在兩端。而 align-items: center
有兩個主要作用:
navbar
中,它讓整個導航欄內的元素(如 Logo 和按鈕)在垂直方向上居中。navbar_brand
中,它確保品牌區域內的元素(如 Logo)也在垂直方向上對齊。margin-left: auto
負責將主題切換按鈕推到最右側,最終達到導航欄整體平衡的佈局效果。
.navbar {
display: flex;
justify-content: space-between; // 左右對齊
align-items: center; // 垂直方向居中
padding: 10px 20px; // 內邊距,保持內容與邊框之間的距離
height: 70%;
max-width: 80%;
margin: 5px auto;
}
.navbar_brand {
display: flex;
align-items: center; // 保證 Logo 與按鈕垂直居中
}
.themeButton {
margin-left: auto; // 將主題按鈕推到右邊
}
這樣的設計確保了導航欄內元素的平衡佈局,無論在淺色模式還是深色模式下,都能夠保持一致的視覺效果。
map-get
進行主題設置接下來,我們在 _theme.scss
使用 Sass 的 map-get
函數實現動態主題切換。map-get
讓我們能夠根據當前的主題模式,從預定義的主題地圖中提取對應的樣式。這樣我們就不需要硬編碼顏色值,減少了重複且更靈活地控制樣式。
$themes: (
"light-mode": ("primary": #FFC0CB, // 淡粉色,用於Basic Stage按鈕
"secondary": #E6E6FA, // 淡紫色,用於Advanced Stage按鈕
"background-primary": #FFFFF0, // 淡黃色, 白色背景
"highlight": #FFD700, // 金黃色,用於亮點或特定強調部分
"text-primary": #1C1C1C, // 深灰色文字,用於標題和主要文字
"text-secondary": #A9A9A9, // 中灰色,用於次要文字
"button-text": #1C1C1C, // 按鈕文字黑色
),
"dark-mode": ("primary": #4A90E2, // 亮藍色,用於Basic Stage按鈕
"secondary": #F5A623, // 亮橙色,用於Advanced Stage按鈕
"background-primary": #2C3E50, // 深藍色背景
"highlight": #FFC107, // 淡黃色,用於月亮或亮點
"text-primary": #FFFFFF, // 白色文字,用於標題和主要文字
"text-secondary": #90A4AE, // 淡灰色文字,用於次要文字
"button-text": #FFFFFF, // 按鈕文字白色
)
);
在 Navbar.module.scss
文件中,為 navbar
元件定義了基於主題的樣式。這裡使用 @mixin
和 map-get
結合的方式,依據當前主題來自動應用樣式,例如動態設定邊框的顏色。
.navbar {
//...其他實作
// 根據主題應用樣式的 Mixin
@mixin theme-container($theme) {
border-bottom: 2px solid map-get(map-get($themes, $theme), "text-primary");
border: 2px solid map-get(map-get($themes, $theme), "text-primary");
}
// light-mode 樣式
&.lightMode {
@include theme-container("light-mode");
}
// dark-mode 樣式
&.darkMode {
@include theme-container("dark-mode");
}
}
我們新增了菜單項目,並使用 menuItems
陣列來定義每個項目及其對應的鏈接。透過 React 的 useState
來管理當前選中的菜單項目,並通過 activeLink
動態地為選中的項目添加 active
樣式,從而實現當前頁面的高亮效果。此外,為了支援響應式設計,我們實現了漢堡菜單切換功能。
const Navbar = () => {
const [activeLink, setActiveLink] = useState(0); // 控制當前選中的菜單項目
const [isMenuOpen, setIsMenuOpen] = useState(false); // 控制漢堡菜單開關
//...其他實作
// 定義選單項目
const menuItems = [
{ name: 'Home', link: '#home' },
{ name: 'About', link: '#about' },
{ name: 'Services', link: '#services' },
{ name: 'Contact', link: '#contact' }
];
return (
<div className={`${styles.navbar}
${isDarkMode ? styles.darkMode : styles.lightMode}`
} >
{/* ...其他實作 */}
{/* 側邊菜單區域 */}
<div className={`${styles.sideMenu} ${isMenuOpen && styles.menuOpen}`}>
<div className={styles.navbarLinks}>
{menuItems.map((item, index) => (
<li key={index} >
<a
href={item.link}
className={activeLink === index ? styles.active : ''}
onClick={() => setActiveLink(index)}
>
{item.name}
</a>
</li>
))}
</div>
{/* 主題切換按鈕 */}
<ThemeButton className={styles.themeButton} />
</div>
{/* 漢堡菜單按鈕 */}
<button className={styles.hamburgerMenu} onClick={toggleMenu}>
{isMenuOpen ? '✕' : '☰'}
</button>
</div>
);
};
export default Navbar;
首先,我們使用 @media
查詢確保側邊選單在螢幕寬度小於 768px 時可以隱藏並在需要時顯示。當點擊漢堡菜單時,透過 menuOpen
顯示側邊選單,這樣可以在行動裝置上提供更佳的使用者體驗。
關鍵點:
menuOpen
的狀態來實現。flex-direction: column
並配合 align-items: center
,將選單項目從原本的水平排列改為垂直排列,確保響應式設計在不同裝置上保持一致的佈局效果。
.sideMenu {
display: flex;
justify-content: space-between; // 預設為讓選單內容均勻分佈
align-items: center;
flex-grow: 1;
}
.hamburgerMenu {
display: none;
font-size: 24px;
background: none;
border: none;
cursor: pointer;
color: inherit;
}
.themeButton {
margin-left: auto; // 讓ThemeButton獨立靠右對齊
}
@media (max-width: 768px) {
.sideMenu {
display: none;
position: absolute;
top: 55px;
right: 0;
margin-right: 5%;
width: 30%;
flex-direction: column;
gap: 10px;
padding: 20px;
align-items: center;
}
.menuOpen {
display: flex;
}
.hamburgerMenu {
display: block;
transition: transform 0.3s ease-in-out;
&:active {
transform: scale(0.9); // 點擊時縮小
}
}
}
最後,我們將為導航欄的選中項目添加一個筆刷效果。這個效果是通過 CSS 偽元素 ::after
來實現的,當某個菜單項目被選中時,在其文字下方會出現一個動態的筆刷背景,增加視覺吸引力。
.navbarLinks li a {
position: relative; // 使文字相對定位,偽元素能相對於它定位
// 當菜單項目被選中時
&.active::after {
content: '';
position: absolute;
left: 0;
bottom: 0; // 確保偽元素貼合文字底部
width: 100%;
height: 0.5em; // 根據需要調整高度,這裡使用字體高度的一半
background-color: map-get(map-get($themes, $theme), "primary"); // 被選中時的顏色;
border-radius: 4px; // 使背景稍微圓潤,模仿筆刷效果
opacity: 0.6; // 調整透明度,使背景不會過於明顯
transition: width 0.3s ease; // 添加動畫效果
}
}
::after
偽元素:用於生成筆刷背景。利用position: absolute;
將偽元素定位在文字下方,height: 0.5em;
則控制了背景的高度,類似筆刷的效果。map-get
從主題配置中動態獲取顏色,並通過 border-radius
來模仿筆刷的圓潤效果。transition: width 0.3s ease;
為偽元素的展開添加平滑動畫,使筆刷效果更加流暢。這樣的筆刷效果可以為當前選中的菜單項目增加動態視覺效果,提升整體導航欄的互動性和美觀度。
今天,我們成功地打造了一個功能齊全的 React 導航欄元件,包含響應式設計、動態導航高亮、筆刷效果以及主題切換等功能。通過當前的主題管理方式,我們能夠應對不同設備上的多種場景,為用戶提供一致的體驗。
然而,隨著應用的擴展,特別是在處理複雜的Figma 設計稿時,使用 Sass
map-get
管理多主題變得難以維護。每次修改主題都需要處理大量重複的樣式代碼,這讓開發變得非常繁瑣。你是否也遇到過類似的挑戰?
接下來,我們將採用 CSS 變數 替代 map-get
,讓主題切換更靈活並更易於維護,以便能夠應對設計稿中的複雜需求,並在多個頁面中輕鬆實現動態主題切換。
此外,我們已將完整的代碼實作與更多練習題上傳至 GitHub,鼓勵大家前往查看,並回顧文章中的概念,挑戰更進階的優化練習。
👉 前往 GitHub 的 v0.12.0-responsive-navbar 查看完整程式碼
✨ 流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!